<?php
session_start();
/*
 * SuRedisAdmin_PHP_1_0
 * 开源作品-PHP写的Redis管理工具(单文件绿色版)-SuRedisAdmin_PHP_1_0
 * Redis在线管理工具
 * 程序特性：
 *      1. 单文件绿色版,部署超简单
 *      2. 支持Redis大部分操作(持续完善中...)
 *      3. 根据Redis大数据量特性进行操作优化
 * 使用方法：
 *      1. 复制本文件到web任意目录
 *      2. 在浏览器输入链接路径,开始使用
 * 说明：写这个程序主要是为了辅助项目中用到的Redis开发.因为项目开发任务繁重,所以这个小程序暂时只能提供浏览、查询、添加、删除等功能(足以应付项目开发需要). 现在,要把精力转移到主项目开发中, 等以后有精力了再继续完善更多功能.如果您有兴趣一起完善这个小程序,欢迎使用git一起开发~感谢！
 * 项目周期：
 *      2015-8-13 立项开发
 *      2015-8-14 框架基本完成
 * 版权：不限商业用途! 如有修改代码请保留原著署名,并把修改后的代码回馈到作者邮箱：<14507247@qq.com>,促进本产品进步.做一个有公德心的正能量程序员~
 *      作者博客：http://www.cnblogs.com/sutroon/ 欢迎交流~
 * @since 1.0 <2015-8-13> SoChishun <14507247@qq.com> Added.
 * @see https://github.com/phpredis/phpredis
 */
$ver = '1.0';
$sess_id = 'sess_suredisadmin';

// 全局配置
$config = array(
    'page_size' => 1000, // 最大显示键名数量(分页条数)
    'default_db' => 'db0', // 默认数据库
);

// 连接redis
$redis = new Redis();
if (is_null($redis)) {
    die('Redis Create Failure!');
}
try {
    $redis->connect('127.0.0.1', 6379);
} catch (Exception $ex) {
    echo 'Redis Connect Failure:', $ex->getMessage();
    exit;
}

// 全局变量
$q = I('q');
$action = I('action');
$message = '';
// 表单操作 2015-8-13 SoChishun Added.
if ($action) {
    $msg = false;
    switch ($action) {
        case 'delete':
            $msg = SuRedisAdmin::action_delete($redis, $key);
            break;
        case 'add':
            $msg = SuRedisAdmin::action_add($redis);
            break;
        case 'copy':
            $msg = SuRedisAdmin::action_copy($redis);
            break;
        case 'modify':
            $msg = SuRedisAdmin::action_modify($redis);
            break;
        case 'search':
            $msg = SuRedisAdmin::action_search($redis);
            break;
        case 'cleardb':
            if (isset($_SESSION[$sess_id])) {
                $db = $_SESSION[$sess_id];
                $db_id = substr($db, 2);
                $redis->select($db_id);
                $redis->flushDB();
                unset($_SESSION[$sess_id]);
                $msg = ajaxMsg(true, '清空数据库[' . $db . ']成功!');
            } else {
                $msg = ajaxMsg(false, '数据库不存在!');
            }
            break;
    }
    if ($msg) {
        $msg_info = $msg['info'];
        if (is_array($msg_info)) {
            $msg_info = implode('<br />', $msg_info);
        }
        $message = $msg['status'] ? '<strong class="green">' . $msg_info . '</strong>' : '<strong class="red">' . $msg_info . '</strong>';
    }
}
?>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>SuRedisAdmin-<?php echo $ver ?></title>
        <script src="//code.jquery.com/jquery-1.11.3.min.js"></script>
        <style type="text/css">
            body {font-size:12px;}
            aside{display:table; padding:5px; float:left;border-right:solid 2px #CCC; max-width:180px;}
            main {display:table;float:left; padding:10px;}
            footer { clear: left; padding:10px; text-align: right; color:#666; border-top:solid 2px #CCC;}
            a {text-decoration: none;}
            h3 a { font-weight: normal; font-size:12px;}
            p { margin:0px; line-height: 18px;}
            ul, li { list-style: none; margin:0px;}
            li { margin-left:-40px; line-height: 18px;}
            .red { color:#F00;}
            .green { color:#090;}
            aside h3 { margin:5px 0px;}
            .table_grid { max-height: 540px; overflow-y:auto;}
            .table_grid table {border: solid 1px #CCC; border-top: none; border-left: none; margin-bottom:10px;}
            .table_grid th, .table_grid td { border:solid 1px #CCC; border-right: none; border-bottom: none; padding:5px;}
        </style>
        <script type="text/javascript">
            /**
             * 显示或隐藏元素
             * @param {type} a
             * @since 1.0 <2015-8-13> SoChishun Added.
             */
            function toggle_next(a) {
                var $a = $(a);
                $a.text('[-]' == $a.text() ? '[+]' : '[-]').parent().next().toggle();
                return false;
            }
        </script>
    </head>
    <body>
        <h1>SuRedisAdmin</h1>
        <fieldset>
            <legend>Redis状态信息 <a href="#" onclick="return toggle_next(this);">[+]</a></legend>
            <?php
            $dbs = array();
// 获取Redis状态信息 2015-8-13 SoChishun Added.
            $info = $redis->info();
            echo '<table style="display:none"><tr>';
            $i = 0;
            foreach ($info as $key => $value) {
                if ($i > 0 && $i % 3 < 1) {
                    echo '</tr><tr>';
                }
                // 解析数据库
                if (0 === strpos($key, 'db')) {
                    $avals = explode(',', $value);
                    foreach ($avals as $sval) {
                        $atmp = explode('=', $sval);
                        $dbs[$key][$atmp[0]] = $atmp[1];
                    }
                }
                echo '<th>', $key, '</th><td>', $value, '</td>';
                $i++;
            }
            echo '</tr></table>';
            ?>
        </fieldset>
        <aside>            
            <p>
                <?php
// 选择数据库 2015-8-14 SoChishun Added.
                $db = I('db'); // [全局变量] 当前数据库名称
                if (!$db) {
                    $db = isset($_SESSION[$sess_id]) ? $_SESSION[$sess_id] : $config['default_db'];
                }
                if (array_key_exists($db, $dbs)) {
                    $_SESSION[$sess_id] = $db;
                    $dbconfig = $dbs[$db]; // [全局变量] 当前数据库信息 array('db'=>'','db_id'=>0,'keys'=>0,'expires'=>0,'avg_ttl'=>0);
                    $dbconfig['db'] = $db;
                    $dbconfig['db_id'] = substr($db, 2);
                    $redis->select($dbconfig['db_id']);
                } else {
                    echo '数据库 [' . $db . '] 不存在!';
                }

                echo '<form method="get">数据库：<select name="db">';
                foreach ($dbs as $key => $value) {
                    echo '<option value="', $key, '"', ($db == $key ? ' selected="selected"' : ''), '>', $key, '</option>';
                }
                echo '</select><button type="submit">转到</button></form>';
                ?>
            <p>
                <a href="?q=view">[新增]</a>
                <a href="?action=cleardb" onclick="return confirm('您确定要清空数据库中[<?php echo $db ?>]所有数据吗?此操作不可复原!');">[清空]</a>
                <a href="?q=search">[搜索]</a>
            </p>
        </p>
        <h3>键名：</h3>
        <ul style="max-height:600px; overflow-y:auto;">
            <?php
// 获取当前数据库所有键值
            $pageconfig['page_size'] = I('pagesize', $config['page_size']);
            $pageconfig['page_id'] = I('pageid', 1);
            SuRedisAdmin::view_aside_keys($redis, &$pageconfig);
            ?>
        </ul>
        <div class="pager" style="max-width: 180px; word-break: break-all; margin-top:5px;" title="手动修改浏览器地址栏参数pagesize可以改变分页尺寸">
            <?php
// 分页功能 2015-8-14 SoChishun Added.
            $pagecount = $pageconfig['page_count'];
            $pagesize = $pageconfig['page_size'];
            echo '行数：', $dbconfig['keys'], ',每页：', $pagesize, ', <br />页次: ', $pageconfig['page_id'], '/', $pagecount, '<br />';
            for ($i = 1; $i <= $pagecount; $i++) {
                echo '<a href="?pagesize=', $pagesize, '&pageid=', $i, '">', $i, '</a> ';
            }
            ?>
        </div>
    </aside>
    <main>
        <?php
        $key = I('key');
        switch ($q) {
            case 'view':
                if ($key) {
                    SuRedisAdmin::view_edit_form($redis, $key);
                } else {
                    SuRedisAdmin::view_add_form();
                }
                break;
            case 'copy':
                if ($key) {
                    SuRedisAdmin::view_copy_form($redis, $key);
                } else {
                    echo '<strong class="red">拷贝失败：键名无效</strong>';
                }
                break;
            case 'search':
                SuRedisAdmin::view_search_form();
                break;
        }
        echo $message;
        ?>
    </main>
    <footer>
        Copyright (c) 2015 SuRedisAdmin;
    </footer>
</body>
</html>
<?php
/* * ****************************************************************************************************
  函数 :)
 * **************************************************************************************************** */

/**
 * 获取浏览器参数
 * @param string $name
 * @param mixed $defv
 * @return mixed
 * @since 1.0 <2015-8-13> SoChishun Added.
 */
function I($name, $defv = '') {
    if (isset($_GET[$name])) {
        return $_GET[$name];
    }
    return isset($_POST[$name]) ? $_POST[$name] : $defv;
}

/**
 * 业务类
 * @since 1.0 <2015-8-13> SoChishun Added.
 */
class SuRedisAdmin {

    /**
     * 显示边栏键值列表视图
     * @param Redis $redis
     * @param array &pageconfig
     * @since 1.0 <2015-8-14> SoChishun Added.
     */
    static function view_aside_keys($redis, &$pageconfig) {
        if (!is_numeric($pageconfig['page_id']) || !is_numeric($pageconfig['page_size'])) {
            die('分页参数无效');
        }
        $pagecount = ceil($dbconfig['keys'] / $pageconfig['page_size']);
        $row_start = ($pageconfig['page_id'] - 1) * $pageconfig['page_size'];
        $row_end = $pageconfig['page_id'] * $pageconfig['page_size'];
        $it = NULL; /* Initialize our iterator to NULL */
        $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY); /* retry when we get no keys back */
        $i = 0;
        $is_break = false;
        while ($akeys = $redis->scan($it)) {
            if ($is_break) {
                break;
            }
            foreach ($akeys as $skey) {
                if ($i > $row_end) {
                    $is_break = TRUE;
                    break;
                }if ($i > $row_start) {
                    $len = 20 - strlen($i);
                    echo '<li>', $i, '.<a href="?q=view&key=', urlencode($skey), '" title="', $skey, '">', (strlen($skey) > $len ? substr($skey, 0, $len) . '..' : $skey), '</a></li>';
                }
                $i++;
            }
            // echo "No more keys to scan!\n";
        }
    }

    /**
     * 显示编辑表单
     * @param Redis $redis Redis实例对象
     * @param string $key 键名
     * @param boolean $is_copy 是否拷贝
     * @since 1.0 <2015-8-13> SoChishun Added.
     */
    static function view_edit_form($redis, $key) {
        echo '<h3>编辑：', $key, ' <a href="?action=delete&key=', urlencode($key), '">[删除]</a><a href="?q=copy&key=', urlencode($key), '">[拷贝]</a></h3>';
        if ($redis->exists($key)) {
            echo '<form method="post">';
            $val = false;
            $type = $redis->type($key); // int
            switch ($type) {
                case Redis::REDIS_HASH:
                    $val = $redis->hGetAll($key);
                    echo '<div class="table_grid"><table cellspacing="0">';
                    foreach ($val as $th => $td) {
                        echo '<tr><th>', $th, '</th><td><input type="text" name="', $th, '" value="', $td, '" /></td></tr>';
                    }
                    echo '<tr><td colspan="2">', json_encode($val), '</td></tr>';
                    echo '</table></div>';
                    break;
                case Redis::REDIS_LIST:
                    $val = $redis->lRange($key, 0, -1);
                    echo '<div class="table_grid"><table cellspacing="0">';
                    foreach ($val as $td) {
                        echo '<tr><td>', $td, '</td></tr>';
                    }
                    echo '<tr><td>', json_encode($val), '</td></tr>';
                    echo '</table></div>';
                    break;
                case Redis::REDIS_NOT_FOUND:
                    break;
                case Redis::REDIS_SET:
                    $val = $redis->sMembers($key);
                    echo '<div class="table_grid"><table cellspacing="0">';
                    echo '<tr><th>值</th></tr>';
                    foreach ($val as $td) {
                        echo '<tr><td>', $td, '</td></tr>';
                    }
                    echo '<tr><td>', json_encode($val), '</td></tr>';
                    echo '</table></div>';
                    break;
                case Redis::REDIS_STRING:
                    $val = $redis->get($key);
                    echo '<div class="table_grid"><table cellspacing="0">';
                    echo '<tr><td><input type="text" name="value" value="', $val, '" /></td></tr>';
                    echo '</table></div>';
                    break;
                case Redis::REDIS_ZSET:
                    echo '<div class="table_grid"><table cellspacing="0">';
                    echo '<tr><th>值</th><th>排序</th></tr>';
                    $val = array();
                    $it = NULL;
                    $redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);
                    while ($arr_matches = $redis->zscan($key, $it, '*')) {
                        foreach ($arr_matches as $str_mem => $f_score) {
                            echo '<tr><th>', $str_mem, '</td><td>', $f_score, '</td></tr>';
                            $val[$str_mem] = $f_score;
                        }
                    }
                    echo '<tr><td colspan="2">', json_encode($val), '</td></tr>';
                    echo '</table></div>';
                    break;
            }
            if ($val) {
                echo '<input type="hidden" name="action" value="modify" />';
                echo '<button type="submit">保存</button><button type="reset">重置</button>';
            }
            echo '</form>';
        } else {
            echo '<div>键名不存在!</div>';
        }
    }

    /**
     * 显示复制表单
     * @param Redis $redis Redis实例对象
     * @param string $key 键名
     * @since 1.0 <2015-8-13> SoChishun Added.
     */
    static function view_copy_form($redis, $key) {
        $type = $redis->type($key); // int
        echo '<h3>拷贝：', $key, '</h3>';
        if ($redis->exists($key)) {
            echo '<form method="post">';
            $val = false;
            switch ($type) {
                case Redis::REDIS_HASH:
                    $val = $redis->hGetAll($key);
                    echo '<div class="table_grid"><table cellspacing="0">';
                    echo '<tr><th>类型：</th><td><input type="text" name="type" value="HASH" readonly="readonly" /></td></tr>';
                    echo '<tr><th>键名：</th><td><input type="text" name="key" value="', $key, '" /><input type="hidden" name="key_old" value="', $key, '" /></td></tr>';
                    echo '<tr><th>键值：</th><td>';
                    echo '<p style="color:#F00;">HASE类型格式：{"id":1,"name":"this is \"my Name\""}<br />LIST类型格式：["name","this is \"my Name\""]<br /></p>';
                    echo '<textarea name="value" cols="90" rows="9">', json_encode($val), '</textarea></td></tr>';
                    echo '</table></div>';
                    break;
                case Redis::REDIS_LIST:
                    $val = $redis->lGet($key);
                    break;
                case Redis::REDIS_NOT_FOUND:
                    break;
                case Redis::REDIS_SET:
                    $val = $redis->sGetMembers($key);
                    break;
                case Redis::REDIS_STRING:
                    $val = $redis->get($key);
                    break;
                case Redis::REDIS_ZSET:
                    $val = $redis->get($key);
                    break;
            }
            if ($val) {
                echo '<input type="hidden" name="action" value="copy" />';
                echo '<button type="submit">保存</button><button type="reset">重置</button>';
            }
            echo '</form>';
        } else {
            echo '<div>键名不存在!</div>';
        }
    }

    /**
     * 显示新增表单
     * @since 1.0 <2015-8-13> SoChishun Added.
     */
    static function view_add_form() {
        echo '<h3>新增数据</h3>';
        echo '<form method="post">';
        echo '<div class="table_grid"><table cellspacing="0">';
        echo '<tr><th>类型：</th><td>';
        $atypes = array('HASH', 'LIST', 'SET', 'STRING', 'ZSET');
        echo '<select name="type">';
        foreach ($atypes as $stype) {
            echo '<option value="', $stype, '">', $stype, '</option>';
        }
        echo '</select>';
        echo '</td></tr>';
        echo '<tr><th>键名：</th><td><input type="text" name="key" /></td></tr>';
        echo '<tr><th>键值：</th><td>';
        echo '<p style="color:#F00;">HASE类型格式：{"id":1,"name":"this is \"my Name\""}<br />LIST类型格式：["name","this is \"my Name\""]<br /></p>';
        echo '<textarea name="value" cols="90" rows="9"></textarea></td></tr>';
        echo '</table></div>';
        echo '<button type="submit">保存</button><button type="reset">重置</button>';
        echo '<input type="hidden" name="action" value="add" />';
        echo '</form>';
    }

    /**
     * 显示查询表单
     * @since 1.0 <2015-8-14> SoChishun Added.
     */
    static function view_search_form() {
        echo '<h3>搜索</h3>';
        echo '<form method="post">';
        echo '<div class="table_grid"><table cellspacing="0">';
        echo '<tr><th>键名：</th><td><p class="red">支持模糊搜索，如：*words*.</p><input type="text" name="key" value="', I('key'), '" /></td></tr>';
        // echo '<tr><th>键值：</th><td><p class="red">支持模糊搜索，如：*words*.</p><input type="text" name="value" /></td></tr>';
        echo '</table></div>';
        echo '<button type="submit">立即搜索</button>';
        echo '<input type="hidden" name="action" value="search" />';
        echo '</form>';
    }

    /**
     * 删除键操作
     * @param Redis $redis
     * @param string $key
     * @return string
     * @since 1.0 <2015-8-13> SoChishun Added.
     */
    static function action_delete($redis, $key) {
        if (!$key) {
            return ajaxMsg(false, '删除失败: 键名不存在!');
        } else {
            $redis->delete($key);
            return ajaxMsg(true, '键 [' . $key . '] 删除成功!');
        }
    }

    /**
     * 表单新增数据操作
     * @param Redis $redis
     * @return array
     * @since 1.0 <2015-8-14> SoChishun Added.
     */
    static function action_add($redis) {
        $data = $_POST;
        $errs = false;
        if ($data['key']) {
            if ($redis->exists($data['key'])) {
                $errs[] = '键值已存在!';
            }
        } else {
            $errs [] = '键名无效!';
        }
        if (strlen($data['value']) < 1) {
            $errs [] = '键值无效!';
        }
        if ($errs) {
            return ajaxMsg(false, $errs);
        }
        $key = $data['key'];
        switch ($data['type']) {
            case 'HASH':
                $avalue = json_decode($data['value']);
                $redis->hMset($key, $avalue);
                break;
            case 'LIST':
                $avalue = json_decode($data['value']);
                foreach ($avalue as $svalue) {
                    $redis->lPush($key, $svalue);
                }
                break;
            case 'SET':
                $avalue = json_decode($data['value']);
                foreach ($avalue as $svalue) {
                    $redis->sAdd($key, $svalue);
                }
                break;
            case 'STRING':
                $redis->set($key, $data['value']);
                break;
            case 'ZSET':
                $avalue = json_decode($data['value']);
                $i = 0;
                foreach ($avalue as $svalue) {
                    $redis->zAdd($key, $i, $svalue);
                    $i++;
                }
                break;
        }
        return ajaxMsg(true, '添加成功');
    }

    static function action_copy($redis) {
        $key = I('key');
        $key_old = I('key_old');
        if ($key == $key_old) {
            return ajaxMsg(false, '复制操作失败：新键名不能和旧键名一样!<br />如果想编辑高级数据，请按照以下步骤操作：<br />1. 删除原键.<br />2. 新增新键.');
        }
    }

    static function action_modify($redis) {
        
    }

    /**
     * 搜索操作
     * @param Redis $redis
     * @return array
     * @since 1.0 <2015-8-14> SoChishun Added.
     */
    static function action_search($redis) {
        $data = $_POST;
        $errs = false;
        if (!$data['key']) {
            $errs [] = '键名无效!';
        }
        if ($errs) {
            return ajaxMsg(false, $errs);
        }
        $key = $data['key'];
        $akeys = $redis->keys($key);
        if ($akeys) {
            $i = 0;
            $items = array();
            $items[] = '<div style="margin-top:5px;">查询结果：</div>';
            foreach ($akeys as $skey) {
                $items[] = $i . '.<a href="?q=view&key=' . urlencode($skey) . '" title="' . $skey . '">' . $skey . '</a>';
                $i++;
            }
            return ajaxMsg(true, $items);
        } else {
            return ajaxMsg(false, '<div style="margin-top:5px;">查询结果：</div><br />没有符合条件的数据! [查询条件：' . $key . '] <a href="?q=search">[返回搜索]</a>');
        }
    }

}

/**
 * 格式化返回消息
 * @param boolean $status
 * @param string $info
 * @return array
 * @since 1.0 <2015-8-13> SoChishun Added.
 */
function ajaxMsg($status, $info) {
    return array('status' => $status, 'info' => $info);
}
?>